■ PCのキーボードスイッチを読み込んで液晶に表示する
<PS2概要>
(1) 全般
パソコンのキーボードの信号はPS2と呼ばれるシリアルインターフェースで通信がおこなわれています。IBMがつくった
2線式クロック同期方式双方向半二重シリアル通信で通信速度は9600bpsです。マウスにも使われています。キー
ボードの各キーにはアスキーコードとはことなるスキャンコードと呼ばれるコードがわりあてられています。パソコンの
場合キーボードが押されると以下のように信号がPCのアプリケーションソフトへと送られていきます。
@ キーボードが押される
Aキーボードマイコン(intel 8051等)がダイナミックスキャンにより押されたキーを特定する
Bキーボードマイコンはスキャンコードを生成して同期クロック(CLK)と共にデータ(DATA)をPS2コネクタに
出力する。
・クロックはDATAを送る時だけ発生し、クロックの速度は9600bpsです。
・DATAは1〜4バイトで 1バイト毎に2msec程度の間隔で区切って送られてくる
・DATAに出力される信号はスキャンコード以外にACKやキーボードのIDなどもある。
C信号がキーボードケーブル経由で伝送される。
Dパソコン側ではキーボードコントローラ(intel 8042等)がPS2シリアル信号をスキャンコードに変換する
EさらにこのスキャンコードをBIOSがASCIIコードに変換する
FBIOS→OS→アプリケーションソフトとASCIIコードが伝送される
【PS2インターフェースに関する技術情報】
★OADGテクニカル・リファレンス
★ Sazanami Online PS/2 インターフェイスの研究
(2) コネクタ & ハードウェア
・ コネクタはミニDINと呼ばれるコネクタが使われています。
・ PC側のコネクタ(メス)のピンアサイン(ピン配列)を以下に示します。2番、6番はNCで4番が電源、3番が
グランド、1番、5番が信号ラインとなっています。
ピン配列
1 … DATA
2 … NC
3 … GND
4 … +5V
5 … CLK
6 … NC
・ 1番DATA信号と5番CLK信号のインターフェースはKeyBoard側、PC側共に電気回路的には全く同じで4.7KΩ
プルアップによるNPNトランジスタオープンコレクタ回路になっています。入力と出力は別ポートの構成となって
います。 したがって PC側の所要ポート数は 合計4ポートとなります。 (下図 左 参照)
実際に製作したものは、少ないポート数でPS2インターフェースを実現する観点からPICの流れ出し電流許容値
が25mAと大きいことを利用して信号ラインに直列にR=150Ωの抵抗を挿入することとしました。(下図 右参照)
★ KeyBoard側のトランジスタがON(ON電圧=0.4V)、PIC側のポートが出力モード”High”の場合を実測
するとPIC側のポートの電圧は3.6Vでした。したがって、この時のPICの出力電流は
I=(3.6V-0.4V)/150Ω = 21.3mA ( < 25mA → OK )
また、PICの入力レベル”High”はTTLレベルの2.0V以上とと規定されています。この面からもチェック
してみますと
3.6V > 2.0V ( → OK )
となります。
★ 一方、KeyBoard側のトランジスタがOFFで PIC側が”Low”の場合のKeyBoard側の出力電圧を実測して
みると めのこ計算値 0.54V (=150Ω/(150Ω+4.7KΩ)×(5V - 0.4V) + 0.4V)より小さな0.4Vで
した。 KeyBoard側のLowレベルのスレッシュホールドレベルはTTLレベルかCMOSレベルかわかりま
せんが
0.4V < 0.8V (TTLレベルの場合 → OK )
0.4V < 1.5V (CMOSレベル場合 at Vcc=+5V → OK )
と問題ないことがわかります。
(3) パルス信号
(3−1) 全般
・ 信号はKeyBoard側からPC側、PC側からKeyBoard側の両方ともKeyBoard側のCLKからのクロックに同期して
信号の授受がおこなわれます。クロックは9600bps→9.6KHz duty=50%で、信号授受をおこなう時だけ出力
されます。
・ 1〜4バイトのデータ(可変長バイト)がやりとりされます。 複数のバイトデータが送られる時は1バイトづつ
2msec程度の間隔をおいておくられます。1バイトのデータは下図のようにスタートビット、8ビットのデータ、
パリティビット、ストップビットから構成されています。パリティは奇数パリティです。
データはLSBから すなわちbit0、bit1、…bit7と送りだされます。 KeyBoard側よりPIC側へ信号が送られ
る場合は CLKのパルスが立ち下がりとなった時にDATA信号は出力されているのでこのタイミングで
割込みによってDATAを読み込みます。また PIC側よりKeyBoard側へ信号を送る時はCLKのパルスが
立ち下がったことを検出した時は、1μsec以内にDATA信号をDATAラインに出力する。
下図はKeyBoard側よりPIC側へ 11111010(2進表示:プログラム上の表記は0b11111010
16進表示で
表記すれば 0xfa)と云うデータを送った場合の例を示しています。
(3−2) スキャンコード
キーボードの各キーにはアスキーコードとは異なるスキャンコードと呼ばれるコードが割り当てられています。
キーボードを押した時(Make)と離した時(Brake)のそれぞれの瞬間にスキャンコードが送信されます。Brakeの
スキャンコードはF0の後にMakeのスキャンコードが追加されたものです。 キーによってはMakeのスキャンコードが
2バイトのものもあります。 したがってこの場合のBrakeのスキャンコードは、3バイトのデータが2msec間隔で
つづけて送られてきます。 (日本語109キーボードのスキャンコード表 参照)
<試作品仕様>
・ キーボードを押して離すと液晶(16文字:英数字&記号 × 2段)に押したキーボードの文字と送られてきた
スキャンコードが16進表示で1バイトづつあとから送られてきた順番に表示されること
★上段 (左隅から) …… 最後に押したキーボードの文字 M[0]L[0] M[1]L[1] M[2]L[2] M[3]L[3]
★下段 (左隅から) …… M[4]L[4] M[5]L[5] M[6]L[6]
M[7]L[7] M[8]L[8]
(注) M[i]L[i] ( i = 0〜8) : 最後から i番前に送られてきた1バイトデータを16進表示したもの
L[i]が下位4ビット、M[i]が上位4ビットをあらわす。
(例) キーボードを”y” ”b ” と 押した後 ”k” を押して指を離すと液晶には以下のように表示される
k 42 f0 42 32
f0 32 35 f0 35
・ シフトキーを押しながらキーボードを押すとシフト文字(アルファベットの大文字など)が表示されること
・ Caps Lock ができること。尚、この時キーボードのLEDが点灯すること
<試作品外観>
下記の写真には上記回路図にはない、また本テーマと関係のない部品が多々写っています。
<回路図>
PIC18F452をつかった場合の回路図を以下に示します。(→回路図ののPDFファイル)
以下のプログラム例の中にある液晶表示器制御ライブラリ 1llcd_lib.cは 後閑哲也さんが設計されたものです。 |
<プログラム例> //-------------------------------------- //-------------------------------------- // 構成 // KeyBoard → PIC18F452 → 16文字キャラクタ液晶 // ★ shift capsLock 対応 // PICからキーボードへの送信 // @ CLK線を60μsec以上インアクティブ(L)にする // A 次にDATA線も60μsec程度インアクティブ(L)にします // B DATA線、CLK線をア60μsec程度クティブ(H)にする // C スタートビットをDATA線に出力し DATA線にキーボードがクロックを出力するのを待つ // D CLK線にクロックの立下りを検出したらDATA線にデータを出力する // E ビット8には奇数パリティを計算して設定する #include "18f452.h" #use delay(clock=40000000) #FUSES EC,NOWDT,NOPROTECT,PUT,BROWNOUT,NOLVP #use RS232(BAUD=9600,XMIT=PIN_C6,RCV=PIN_C7) // TX=RC6,RX=RC7 #byte INTCON3=0xFF1 //アドレス0xFF1の変数をINTCON3と命名 #byte TRISB=0xF93 #define Transmit 1 #define Receive 0 //////// Port define and link LCD library #define mode 0 // No1 液晶 #define input_x input_D #define output_x output_D #define set_tris_x set_tris_D #define rs PIN_D2 //chip select #define stb PIN_D0 //strobe #include <1lcd_lib.c> #include "ScanTable1.h" unsigned long int BitReceiveData = 0,BitSendData = 0; int BitReceiveCount = 0,ix,TRMode = Receive,BitSendCount =0; char ScanData[100],AsciiData[100]; short int Flag,Shift = 0,Caps = 0,CapsNow = 0,CapsTrigger =0,LockTrigger = 0; unsigned long int temp; void Lcd() //液晶表示 { lcd_cmd(0x01); // 全消去 printf(lcd_data,"%c %x %x %x %x", AsciiData[ix-1],ScanData[ix-1],ScanData[ix-2],ScanData[ix-3],ScanData[ix-4]); // 他なら 文字表示 lcd_cmd(0xC0);//2行目の先頭へ printf(lcd_data," %x %x %x %x %x", ScanData[ix-5],ScanData[ix-6],ScanData[ix-7],ScanData[ix-8],ScanData[ix-9]); } unsigned long int Parityx(unsigned long int Datax) // パリティ計算 { unsigned long int i = 0,Parity = 0; for(i = 0; i < 8; i ++ ) { if(bit_test(Datax,i) == 1)Parity = Parity + 1; } if((Parity % 2) == 1) Parity = 0; else Parity = 1; return Parity; } void BitSendDataSet() //奇数パリティデータのセット 及び 送信データのセット { unsigned long int Parity; Parity = Parityx(BitSendData); if(Parity == 1)bit_set(BitSendData,8); else bit_clear(BitSendData,8); BitSendData =BitSendData << 1; } void TransmitModeSet(){ //キーボード側に送信したい旨連絡する set_tris_b(0x00); //B0(DATA線)、 B1(CLK線)をともに出力ポートに設定 output_low(PIN_B1); // CLK線→L output_high(PIN_B0); delay_us(60); output_low(PIN_B0); // DATA線→L delay_us(60); //60μsecごとにキーボードはラインをチェックするので15msec待つ //キーボードi8042はホストがデータを送信しようとしていることを認識したはず set_tris_b(0b00000010); //B0(DATA線)を出力ポートに B1(CLK線)を入力ポートに設定 //キーボードからrのクロックに同期してB0からの出力の準備をおこなう output_low(PIN_B0); // DATA線→L TRMode = Transmit; } void Transmitx() //送信関数 { output_bit(PIN_B0,(bit_test(BitSendData,0))); BitSendData = BitSendData >> 1; BitSendCount++; if(BitSendCount >= 12){ TRMode = Receive; // BitSendCount = 0; } } void Receivex(){ //受信関数 temp = (unsigned long int) input(PIN_B0); // Clock H --> L if((Flag == 0) && (temp == 0))Flag = 1; if(Flag == 1) { BitReceiveData = (BitReceiveData >> 1) ;//このビットシフト回数は9回である。10回ではない temp = (temp << 10); BitReceiveData = BitReceiveData + temp ; BitReceiveCount++; if(BitReceiveCount >= 10) { BitReceiveData = BitReceiveData >> 2; // 下2桁の無効データを削除 ScanData[ix] = (byte)(BitReceiveData & 0xFF); //-------------------シフトキーがおされているかどうかの判別 if( (ScanData[ix] == 0x12)||(ScanData[ix] == 0x59))Shift = 1;// シフトキーMake操作の場合 if( ((ScanData[ix] == 0x12)||(ScanData[ix] == 0x59)) && (ScanData[ix-1] == 0xF0))Shift = 0;// シフトキーBreak操作の場合 //-----Capsロック判別 //-------Caps Lock キーが押されているかどうかの判別 if(ScanData[ix] == 0x58) { CapsNow = 1; } if( (ScanData[ix] == 0x58) && (ScanData[ix-1] == 0xF0))// CapsLock : Brake { CapsNow = 0; } if( ( (ScanData[ix] == 0x58) && !((ScanData[ix-1] == 0xf0) || (ScanData[ix-1] == 0x58) ) ) || ((ScanData[ix] == 0x58) && (ScanData[ix-1] == 0x58) && (ScanData[ix-2] == 0xf0)) ) { CapsTrigger = 1; } else CapsTrigger = 0; //---------------Caps Lock 状態かどうかの判別 if( (CapsTrigger == 1) && (Shift == 1)) { LockTrigger = 1; } else LockTrigger = 0; AsciiData[ix] = ScanToAscii(ScanData[ix],(Shift | Caps)); // L R Shift or Caps if((ScanData[ix] == 0x12) || (ScanData[ix] == 0x59) || (ScanData[ix] == 0x58)) { AsciiData[ix] = AsciiData[ix -2]; } if( (ScanData[ix] <= 0x80) ) { lcd_cmd(0x01); // 全消去 printf(lcd_data,"%c %x %x %x %x", AsciiData[ix],ScanData[ix],ScanData[ix-1],ScanData[ix-2],ScanData[ix-3]); // 他なら 文字表示 lcd_cmd(0xC0);//2行目の先頭へ printf(lcd_data," %x %x %x %x %x", ScanData[ix-4],ScanData[ix-5],ScanData[ix-6],ScanData[ix-7],ScanData[ix-8]); } ix++; if(ix >=100)ix =0; BitReceiveCount = 0; BitReceiveData = 0x0000; Flag = 0; } } } #INT_EXT1 //B1:Clk void KeyBoardClk() { switch(TRMode){ case Receive:// スタンバイ状態 キーボードからの信号待ち Receivex(); break; case Transmit:// キーボードへの送信モード Transmitx(); break; default: break; } } void CapsLockLed() //CapsLockLedのON/OFF制御 { if(LockTrigger == 1) { disable_interrupts(INT_EXT1); disable_interrupts(GLOBAL); BitSendData = 0xFFED; BitSendDataSet(); TransmitModeSet(); enable_interrupts(INT_EXT1); // Set up EXT1 enable_interrupts(GLOBAL); do { }while(ScanData[ix-1] != 0xfa); ScanData[ix-1] = 0xf8; disable_interrupts(INT_EXT1); disable_interrupts(GLOBAL); if(Caps == 0) { BitSendData = 0xFF04; //CapsLockLED ON Caps = 1; // } else { BitSendData = 0xFF00; //CapsLockLED OFF Caps = 0; } BitSendDataSet(); TransmitModeSet(); enable_interrupts(INT_EXT1); // Set up EXT1 enable_interrupts(GLOBAL); do { }while(ScanData[ix-1] != 0xfa); if(ScanData[ix-2] == 0xf8)ScanData[ix-2] = 0xfa; LockTrigger = 0; } } void InitialCheck() //電源投入時のPICとキーボード間の送受信 { //------- リセット命令 FF BitSendData = 0xFFFF; //キーボードリセット命令 BitSendDataSet(); //パリティ計算を行い送信データをセットする TransmitModeSet(); //キーボード側に送信したい旨連絡し 送信モードでINT_EXT1にクロックがくるのを待つ enable_interrupts(INT_EXT1); // INT_EXT1が外部割込みを検出できるように割込みを許可する enable_interrupts(GLOBAL); do{ //最初にAck(0xFA : コマンドに対する正しく受け取られた応答)がキーボードからかえってくる }while(ScanData[ix-1] != 0xAA);//その後BAT(0xAA:Basic Assurance Test)が正しく終了したがキーボードからかえってくる disable_interrupts(INT_EXT1); disable_interrupts(GLOBAL); BitSendData = 0xFFF2; //識別情報(キーボードorマウス? どのようなキーボードか?) を要求 BitSendDataSet();//パリティ計算を行い送信データをセットする TransmitModeSet();//キーボード側に送信したい旨連絡し 送信モードでINT_EXT1にクロックがくるのを待つ enable_interrupts(INT_EXT1); // INT_EXT1が外部割込みを検出できるように割込みを許可する enable_interrupts(GLOBAL); do{ //最初にAck(0xFA : コマンドに対する正しく受け取られた応答)がキーボードからかえってくる }while(ScanData[ix-1] != 0x83);//日本語キーボードの場合0xAB,0x83がかえってくる disable_interrupts(INT_EXT1); disable_interrupts(GLOBAL); BitSendData = 0xFFED; //キーボードのLED点灯、消灯要求 BitSendDataSet(); TransmitModeSet(); enable_interrupts(INT_EXT1); // Set up EXT1 enable_interrupts(GLOBAL); do{ }while(ScanData[ix-1] != 0xfa); ScanData[ix-1] = 0xf8; disable_interrupts(INT_EXT1); disable_interrupts(GLOBAL); BitSendData = 0xFF00; BitSendDataSet(); TransmitModeSet(); enable_interrupts(INT_EXT1); enable_interrupts(GLOBAL); do{ }while(ScanData[ix-1] != 0xfa); if(ScanData[ix-2] == 0xf8)ScanData[ix-2] = 0xfa; Lcd(); //液晶表示 } main(){ TRISB = 0xFF;//B port all input mode INTCON3 = 0b01000000;// 割込みレベル : 高位 (bit7)(bit6)(bit5)(bit4)(bit3)(bit2)(bit1)(bit0) // = (X2割込レベル)(X1割込レベル)(意味無)(X2割込許可)(X1割込許可)(意味無)(X2割込フラグ)(X1割込フラグ) ext_int_edge( 1, H_TO_L); // PIN_B1 からの外部割込み lcd_init(); lcd_cmd(0b00001100); // カーソル:OFF ブリンク:OFF lcd_clear(); printf(lcd_data,"start!!"); delay_ms(1000); // KeyBoard POR timeは MAX500msec InitialCheck(); //電源投入時のPICとキーボード間の送受信 while(1) { CapsLockLed(); //CapsLockLedのON/OFF制御 } return 0; } //------------------------------------------------------------------------------------------------- //******************************************* //インクルードファイル ScanTable1.h //******************************************* char ShiftAscii(char ScanData) { char temp; switch(ScanData) { case 0x16: temp = '!'; break; case 0x1E: temp = '"'; break; case 0x26: temp = '#'; break; case 0x25: temp = '$'; break; case 0x2E: temp = '%'; break; case 0x36: temp = '&'; break; case 0x3D: temp = 0x27; break; case 0x3E: temp = '('; break; case 0x46: temp = ')'; break; case 0x45: temp = 0x7E; break; case 0x1C: temp = 'A'; break; case 0x32: temp = 'B'; break; case 0x21: temp = 'C'; break; case 0x23: temp = 'D'; break; case 0x24: temp = 'E'; break; case 0x2B: temp = 'F'; break; case 0x34: temp = 'G'; break; case 0x33: temp = 'H'; break; case 0x43: temp = 'I'; break; case 0x3B: temp = 'J'; break; case 0x42: temp = 'K'; break; case 0x4B: temp = 'L'; break; case 0x3A: temp = 'M'; break; case 0x31: temp = 'N'; break; case 0x44: temp = 'O'; break; case 0x4D: temp = 'P'; break; case 0x15: temp = 'Q'; break; case 0x2D: temp = 'R'; break; case 0x1B: temp = 'S'; break; case 0x2C: temp = 'T'; break; case 0x3C: temp = 'U'; break; case 0x2A: temp = 'V'; break; case 0x1D: temp = 'W'; break; case 0x22: temp = 'X'; break; case 0x35: temp = 'Y'; break; case 0x1A: temp = 'Z'; break; case 0x4E: temp = '='; break; case 0x55: temp = '~'; break; case 0x6A: temp = '|'; break; case 0x54: temp = '`'; break; case 0x5B: temp = '{'; break; case 0x4C: temp = '+'; break; case 0x52: temp = ':'; break; case 0x5D: temp = '}'; break; case 0x41: temp = '<'; break; case 0x49: temp = '>'; break; case 0x4A: temp = '?'; break; case 0x51: temp = '_'; break; case 0x29: temp = ' '; break; case 0x71: temp = '.'; break; case 0x7B: temp = '-'; break; case 0x79: temp = '+'; break; default: temp = ScanData; break; } return temp; } char NoShiftAscii(char ScanData) { char temp; switch(ScanData) { case 0x16: temp = '1'; break; case 0x1E: temp = '2'; break; case 0x26: temp = '3'; break; case 0x25: temp = '4'; break; case 0x2E: temp = '5'; break; case 0x36: temp = '6'; break; case 0x3D: temp = '7'; break; case 0x3E: temp = '8'; break; case 0x46: temp = '9'; break; case 0x45: temp = '0'; break; case 0x1C: temp = 'a'; break; case 0x32: temp = 'b'; break; case 0x21: temp = 'c'; break; case 0x23: temp = 'd'; break; case 0x24: temp = 'e'; break; case 0x2B: temp = 'f'; break; case 0x34: temp = 'g'; break; case 0x33: temp = 'h'; break; case 0x43: temp = 'i'; break; case 0x3B: temp = 'j'; break; case 0x42: temp = 'k'; break; case 0x4B: temp = 'l'; break; case 0x3A: temp = 'm'; break; case 0x31: temp = 'n'; break; case 0x44: temp = 'o'; break; case 0x4D: temp = 'p'; break; case 0x15: temp = 'q'; break; case 0x2D: temp = 'r'; break; case 0x1B: temp = 's'; break; case 0x2C: temp = 't'; break; case 0x3C: temp = 'u'; break; case 0x2A: temp = 'v'; break; case 0x1D: temp = 'w'; break; case 0x22: temp = 'x'; break; case 0x35: temp = 'y'; break; case 0x1A: temp = 'z'; break; case 0x4E: temp = '-'; break; case 0x55: temp = '^'; break; case 0x6A: temp = 0x5C; break;// ¥ case 0x54: temp = '@'; break; case 0x5B: temp = '['; break; case 0x4C: temp = ';'; break; case 0x52: temp = ':'; break; case 0x5D: temp = ']'; break; case 0x41: temp = ','; break; case 0x49: temp = '.'; break; case 0x4A: temp = '/'; break; case 0x51: temp = 0x5C; break;//¥ case 0x29: temp = ' '; break; case 0x71: temp = '.'; break; case 0x7B: temp = '-'; break; case 0x79: temp = '+'; break; default: temp = ScanData; break; } return temp; } char ScanToAscii( char ScanData ,int Shift ) { char AsciiTemp; if(Shift == 1) { AsciiTemp = ShiftAscii(ScanData); } else AsciiTemp = NoShiftAscii(ScanData); return AsciiTemp ; } //--------------------------------------------------------------------------------------------- //************************************** //インクルードファイル 1lcd_lib.c //このファイルは後閑哲也さんが設計されたものです //************************************** /////////////////////////////////////////////// // 液晶表示器制御ライブラリ // 内蔵関数は以下 // lcd_init() ----- 初期化 // lcd_cmd(cmd) ----- コマンド出力 // lcd_data(chr) ----- 1文字表示出力 // lcd_clear() ----- 全消去 //////// データ出力サブ関数 void lcd_out(int code, int flag) { output_x((code & 0xF0) | (input_x() & 0x0F)); if (flag == 0) output_high(rs); //表示データの場合 else output_low(rs); //コマンドデータの場合 delay_cycles(4); //NOP 1 output_high(stb); //strobe out delay_cycles(8); //NOP 2 output_low(stb); //reset strobe } //////// 1文字表示関数 void lcd_data(int asci) { lcd_out(asci, 0); //上位4ビット出力 lcd_out(asci<<4, 0); //下位4ビット出力 delay_us(50); //50μsec待ち } /////// コマンド出力関数 void lcd_cmd(int cmd) { lcd_out(cmd, 1); //上位4ビット出力 lcd_out(cmd<<4, 1); //下位4ビット出力 delay_ms(2); //2msec待ち } /////// 全消去関数 void lcd_clear() { lcd_cmd(0x01); //初期化コマンド出力 delay_ms(15); //15msec待ち } /////// 初期化関数 void lcd_init() { set_tris_x(mode); //モードセット delay_ms(15); lcd_out(0x30, 1); //8bit mode set delay_ms(5); lcd_out(0x30, 1); //8bit mode set delay_ms(1); lcd_out(0x30, 1); //8bit mode set delay_ms(1); lcd_out(0x20, 1); //4bit mode set delay_ms(1); lcd_cmd(0x2E); //DL=0 4bit mode lcd_cmd(0x08); //display off C=D=B=0 lcd_cmd(0x0D); //display on C=D=1 B=0 lcd_cmd(0x06); //entry I/D=1 S=0 lcd_cmd(0x02); //cursor home } //----------------------------------------------------------------------------------------------- <動作結果> ・以下に キーボードから”y”、”b ” と キーインして ”k” を押して指を離した場合の液晶表示を 示します。